/**
 * 
 */
package com.xpec.c4.game.utility;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map.Entry;
import java.util.NavigableMap;
import java.util.TreeMap;

import org.apache.commons.logging.Log;

/**
 * 處理「用權重選擇機制來隨機選取項目」的類別
 * <p>
 * <ul>
 * 使用方式
 * <li>將權重和對應項目加入 {@link #addWeightInfo(int, Object)}
 * <li>透過權重值隨機選取對應項目 {@link #randomSelect()}
 * </ul>
 * 
 * @author TW15010
 */
public class WeightSelection<T> {
	/**
	 * 存放權重資訊的 map<br>
	 * key: 累加上的權重<br>
	 * value: 待選取的物件
	 */
	private NavigableMap<Integer, T> weightItemMap = new TreeMap<Integer, T>();

	/**
	 * 權重值 map<br>
	 * key: 待選取物件的 hash code<br>
	 * value: 待選取物件的原始資料
	 */
	private HashMap<Integer, WeightItemInfo<T>> weightItemInfoMap = new HashMap<Integer, WeightItemInfo<T>>();

	/**
	 * 存放所有加進來的物件資料, 不會被選取後刪除的條件影響<br>
	 * value: 待選取物件的原始資料
	 */
	private ArrayList<WeightItemInfo<T>> orgWeightItemInfoList = new ArrayList<WeightItemInfo<T>>();

	/**
	 * 所有物件的權重加起來的總權重值
	 */
	private int totalWeight = 0;

	/**
	 * 是否要在選取後將物件刪除
	 */
	private boolean removeAfterSelect = false;

	@Override
	public String toString() {
		return weightItemMap.toString();
	}

	/**
	 * 將權重資訊和對應物件加入
	 * 
	 * @param weightValue
	 *            權重值
	 * @param object
	 *            物件
	 */
	public void addWeightInfo(int weightValue, T object) {
		if (weightValue <= 0) {
			return;
		}

		add(weightValue, object);

		WeightItemInfo<T> wi = new WeightItemInfo<T>();
		wi.setWeight(weightValue);
		wi.setObject(object);
		weightItemInfoMap.put(object.hashCode(), wi);

		orgWeightItemInfoList.add(wi);
	}

	/**
	 * 將物件加入待選取 map
	 * 
	 * @param weightValue
	 *            權重值
	 * @param object
	 *            物件
	 */
	private void add(int weightValue, T object) {
		int key = totalWeight + weightValue;

		weightItemMap.put(key, object);

		totalWeight += weightValue;
	}

	/**
	 * 直接從 ArrayList 中把所有的資料加進來
	 * 
	 * @param dataList
	 *            ArrayList 資料列表<br>
	 * @param getWeightValueMethodName
	 *            取得權重值的 method name
	 * @param log
	 *            log 物件, 出錯的時候寫 log 用
	 */
	public void addFromArrayList(ArrayList<T> dataList, String getWeightValueMethodName, Log log) {
		Class<?> clz = null;
		Method method = null;

		for (T obj : dataList) {
			if (clz == null) {
				clz = obj.getClass();
				try {
					method = clz.getMethod(getWeightValueMethodName, new Class<?>[0]);
				} catch (Exception e) {
					if (log != null) {
						if (log.isErrorEnabled()) {
							log.error("get method: " + getWeightValueMethodName + " fail.", e);
						}
					}
				}
			} // if (clz == null) {

			if (method != null) {
				int weightValue = 0;
				try {
					weightValue = (int) method.invoke(obj, new Object[0]);
				} catch (Exception e) {
					if (log != null) {
						if (log.isErrorEnabled()) {
							log.error("invoke method: " + getWeightValueMethodName + " fail.", e);
						}
					}
				}

				addWeightInfo(weightValue, obj);
			} // if (method != null) {
		} // for (T obj : dataList) {
	}

	/**
	 * 清除所有紀錄的資料
	 */
	public void clear() {
		weightItemMap.clear();
		weightItemInfoMap.clear();
		orgWeightItemInfoList.clear();
		totalWeight = 0;
	}

	/**
	 * 透過權重值範圍隨機選取物件
	 * 
	 * @return 對應的物件
	 */
	public T randomSelect() {
		if (totalWeight == 0) {
			return null;
		}

		int randomWeightValue = GameUtility.randomInt(1, totalWeight);

		T obj = get(randomWeightValue);

		// 需要在選取後將物件刪除
		if (removeAfterSelect) {
			remove(randomWeightValue);
		}

		return obj;
	}
	
	/**
	 * 透過權重值範圍隨機選取物件
	 * @param count 次數
	 * @return
	 */
	public ArrayList<T> randomSelect(int count) {
		if (totalWeight == 0) {
			return null;
		}
		
		ArrayList<T> results = new ArrayList<T>();
		
		for (int i = 0; i < count; i++) {
			int randomWeightValue = GameUtility.randomInt(1, totalWeight);

			T obj = get(randomWeightValue);
			
			results.add(obj);
			
			// 需要在選取後將物件刪除
			if (removeAfterSelect) {
				remove(randomWeightValue);
			}
		}
		
		return results;
	}
		
	/**
	 * 可以自己指定一個 key 來取對應的資料出來
	 * 
	 * @param selectKey
	 *            範圍 1 ~ {@link #getTotalWeight()} 的值
	 * @return 對應的資料
	 */
	public T get(int selectKey) {
		if (selectKey <= 0) {
			return null;
		}

		Entry<Integer, T> entry = weightItemMap.ceilingEntry(selectKey);
		if (entry == null) {
			return null;
		}

		T object = entry.getValue();

		return object;
	}

	/**
	 * 移除指定的項目
	 * 
	 * @param selectKey
	 */
	private void remove(int selectKey) {
		Integer key = weightItemMap.ceilingKey(selectKey);
		T obj = weightItemMap.remove(key);

		// 有刪除就要重建權重資料
		if (obj != null) {
			weightItemInfoMap.remove(obj.hashCode());

			// 清空隨機選取用的 map
			weightItemMap.clear();
			totalWeight = 0;

			// 把剩下的再倒回去
			if (weightItemInfoMap.size() > 0) {
				// 還有東西就用剩的倒進去
				for (Entry<Integer, WeightItemInfo<T>> entry : weightItemInfoMap.entrySet()) {
					WeightItemInfo<T> wi = entry.getValue();
					add(wi.getWeight(), wi.getObject());
				}
			} else {
				// 空了, 全部重倒一次
				for (WeightItemInfo<T> wi : orgWeightItemInfoList) {
					add(wi.getWeight(), wi.getObject());
					weightItemInfoMap.put(wi.getObject().hashCode(), wi);
				}
			}
		}
	}

	public int getTotalWeight() {
		return totalWeight;
	}

	public boolean isRemoveAfterSelect() {
		return removeAfterSelect;
	}

	public void setRemoveAfterSelect(boolean removeAfterSelect) {
		this.removeAfterSelect = removeAfterSelect;
	}

	/**
	 * 取得所有可選取物件的列表
	 * 
	 * @return
	 */
	public ArrayList<T> getSelectItemArray() {
		ArrayList<T> list = new ArrayList<T>();

		for (WeightItemInfo<T> item : orgWeightItemInfoList) {
			list.add(item.getObject());
		}

		return list;
	}
}